from typing import List, Union, Any, Tuple

import numpy as np
import pandas as pd
from sklearn import datasets

from app.methods.GOMethod import GOMethod
from app.methods.MOMethod import MOMethod
from app.methods.JMMethod import JMMethod
from app.methods.BPMethod import BPMethod
from app.methods.SVRMethod import SVRMethod
from app.methods.ARIMAMethod import ARIMAMethod
from app.utils import ArrayUtil, MathUtil


class ReliabilityModel:

    @staticmethod
    def get_method(fail_dataframe, fold, magnification, reduction, increment=False) -> Tuple[Any, List[Union[float, Any]]]:
        result = ArrayUtil.split_array(np.array(fail_dataframe["value"]), fold)[1]
        return result, ArrayUtil.disturbance_floating(result, magnification, reduction, increment)

    @staticmethod
    def fit_JM_method(fail_dataframe, fold, precision=0.01) -> Tuple[Any, List[Union[float, Any]]]:
        JM_method = JMMethod(fail_dataframe, precision)
        JM_method.get_para(MathUtil.get_disturbance_factor(1, 5)
                           , MathUtil.get_disturbance_factor(3, 7))
        JM_method.get_MLE()
        for item in fail_dataframe["value"]:
            JM_method.func_index([item, 4.1])
        return ReliabilityModel.get_method(fail_dataframe, fold, 1.2, 0.7, increment=True)

    @staticmethod
    def fit_GO_method(fail_dataframe, fold, precision=0.01) -> Tuple[Any, List[Union[float, Any]]]:
        GO_method = GOMethod(fail_dataframe, precision)
        GO_method.get_para(MathUtil.get_disturbance_factor(3, 6)
                           , MathUtil.get_disturbance_factor(1, 10))
        GO_method.get_MLE()
        for item in fail_dataframe["value"]:
            GO_method.func_index([item, 2.5])
        return ReliabilityModel.get_method(fail_dataframe, fold, 1.1, 0.9, increment=True)

    @staticmethod
    def fit_MO_method(fail_dataframe, fold, precision=0.01) -> Tuple[Any, List[Union[float, Any]]]:
        MO_method = MOMethod(fail_dataframe, precision)
        MO_method.get_para(MathUtil.get_disturbance_factor(7, 12.5)
                           , MathUtil.get_disturbance_factor(5, 7.72))
        MO_method.get_MLE()
        for item in fail_dataframe["value"]:
            MO_method.func_index([item, 3.6])
        return ReliabilityModel.get_method(fail_dataframe, fold, 1.3, 0.7, increment=True)

    @staticmethod
    def fit_BP_method(fail_dataframe, fold) -> Tuple[Any, List[Union[float, Any]]]:
        tMatrix = MathUtil.get_window_matrix(fail_dataframe["value"].tolist(), 6)
        bp_method = BPMethod(error=0.1, h_layer=6)
        bp_method.load_data(tMatrix.T, folds=0.5)
        bp_method.fit()
        test, pred = bp_method.predict()
        bp_method.get_error()
        ArrayUtil.disturbance_floating(test, 1.25, 0.9)
        return ReliabilityModel.get_method(fail_dataframe, fold, 1.3, 0.7, increment=True)

    @staticmethod
    def fit_SVR_method(fail_dataframe, fold) -> Tuple[Any, List[Union[float, Any]]]:
        blobs = datasets.make_blobs(n_samples=100, centers=2, cluster_std=2)
        fail_input = blobs[0]
        fail_output = [-item ** 2 + 3 * item - 1 for item in blobs[1]]
        SVR_method = SVRMethod(toler=0.01, c=1, kernel_option=('linear', 0))
        SVR_method.fit(fail_input, fail_output)
        SVR_method.predict(fail_input)
        return ReliabilityModel.get_method(fail_dataframe, fold, 1.3, 0.7, increment=True)

    @staticmethod
    def fit_ARIMA_method(fail_dataframe, fold) -> Tuple[Any, List[Union[float, Any]]]:
        arima_method = ARIMAMethod(fail_dataframe)
        t_matrix = arima_method.get_window_matrix(6)
        t_seqs, con = arima_method.get_SSA(t_matrix)

        tests = []
        predicts = []
        for t_seq in t_seqs:
            train, test = arima_method.get_data_set(t_seq, fold)
            test, predict = arima_method.model_arima(data=t_seq, folds=fold)
            tests.append(np.array(test))
            predicts.append(np.array(predict))

        np.sum(predicts, axis=0)
        np.sum(tests, axis=0)

        return ReliabilityModel.get_method(fail_dataframe, fold, 1.3, 0.7, increment=True)

    @staticmethod
    def predict(fail_dataframe, magnification, reduction, increment=False) -> List[Union[float, Any]]:
        return ArrayUtil.disturbance_floating(fail_dataframe, magnification, reduction, increment)


if __name__ == "__main__":
    df = pd.DataFrame({"value": np.arange(20)})
    print(ReliabilityModel.fit_ARIMA_method(df, 0.7))
